﻿/**
 *  Distributed as part of Free.iso8583
 *  
 *  Free.iso8583 is ISO 8583 Message Processor library that makes message parsing/compiling esier.
 *  It will convert ISO 8583 message to a model object and vice versa. So, the other parts of
 *  application will only do the rest effort to make business process done.
 *  
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License or (at your option) any later version. 
 *  See http://gnu.org/licenses/lgpl.html
 *
 *  Developed by AT Mulyana (atmulyana@yahoo.com) 2009-2010
 *  The latest update can be found at sourceforge.net
 *  
 * Use MessageListener class instead of this class for more multi-threaded processes
 **/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Free.iso8583
{
    public class Iso8583Server
    {
        public const int PORT = 5000;
        public const int MAX_CONNECTIONS = 50;

        private static TcpListener _server = null;
        private static ManualResetEvent _tcpClientConnected = new ManualResetEvent(false);
        private static volatile bool _stopped = false;

        public static int Port { get; set; }
        public static int MaxConnections { get; set; }

        internal static void DoBeginAcceptTcpClient()
        {
            _tcpClientConnected.Reset();
            _server.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), _server);
            _tcpClientConnected.WaitOne();
            if (_stopped) return;
            DoBeginAcceptTcpClient();
        }

        internal static void DoAcceptTcpClientCallback(IAsyncResult ar)
        {
            try
            {
                TcpListener listener = (TcpListener)ar.AsyncState;
                TcpClient client = listener.EndAcceptTcpClient(ar);
                
                _tcpClientConnected.Set();

                //Logger.GetInstance().WriteLine("Client connected at " + DateTime.Now);
                ResponseMessageSender rms = new ResponseMessageSender(client);
                rms.RegisterMe();
                rms.AcceptMessage();
            }
            catch (Exception e)
            {
                Logger.GetInstance().Write(e);
            }
        }

        public static void Start()
        {
            //Logger.GetInstance().WriteLine("Loads configuration...");
            try
            {
                MessageProcessor.GetInstance().Load();
            }
            catch
            {
                Logger.GetInstance().WriteLine("Bad configuration, application can't continue.");
                return;
            }

            try
            {
                if (Port <= 0) Port = PORT;
                if (MaxConnections <= 0) MaxConnections = MAX_CONNECTIONS;

                _server = new TcpListener(IPAddress.Any, Port);
                Logger.GetInstance().WriteLine("Start listening port " + Port + " ...");
                if (MaxConnections == 0) _server.Start();
                else _server.Start(MaxConnections);
                
                _stopped = false;
                DoBeginAcceptTcpClient();
            }
            catch (SocketException e)
            {
                Logger.GetInstance().Write(e);
                Stop();
                throw e;
            }
        }

        public static void Stop()
        {
            if (_stopped) return;
            RequestStop();
            ResponseMessageSender.CloseAll();
            if (_server != null) _server.Stop();
        }

        private static void RequestStop()
        {
            _stopped = true;
            _tcpClientConnected.Set();
        }
    }

    internal class ResponseMessageSender : IMessageStream
    {
        private static IDictionary<TcpClient, ResponseMessageSender> tcpClients = new Dictionary<TcpClient, ResponseMessageSender>();
        private TcpClient _client = null;

        private ResponseMessageSender()
        {
        }

        public ResponseMessageSender(TcpClient client)
        {
            _client = client;
        }

        public void RegisterMe()
        {
            lock (((ICollection)tcpClients).SyncRoot)
            {
                tcpClients[_client] = this;
            }
        }

        public void UnregisterMe()
        {
            lock (((ICollection)tcpClients).SyncRoot)
            {
                tcpClients.Remove(_client);
            }
        }

        public void AcceptMessage()
        {
            NetworkStream stream = _client.GetStream();
            byte[] bytes = new byte[1024];
            byte[] b;
            List<byte> data = new List<byte>();
            int i;
            //Logger.GetInstance().WriteLine("Reading message at " + DateTime.Now);
            while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
            {
                if (i != bytes.Length)
                {
                    b = new byte[i];
                    Array.Copy(bytes, 0, b, 0, i);
                }
                else
                {
                    b = bytes;
                }
                data.AddRange(b);
                //Logger.GetInstance().WriteLine(data.Count + " bytes accepted at " + DateTime.Now);
                if (!stream.DataAvailable) break;
            }

            //Logger.GetInstance().WriteLine("Parsing message at " + DateTime.Now);
            MessageProcessor.GetInstance().Receive(data.ToArray(), this);
        }
        
        #region IMessageSender Members
        public byte[] Send(byte[] message)
        {
            if (_client == null)
                throw new MessageListenerException("Socket has been closed.");

            //Logger.GetInstance().WriteLine("Sending response message at " + DateTime.Now);
            _client.GetStream().Write(message, 0, message.Length);
            UnregisterMe();
            Close();
            return null;
        }
        
        public int Receive(byte[] buffer, int offset, int count)
        {
            if (_client == null) throw new MessageListenerException("Socket has been closed.");
            return _client.GetStream().Read(buffer, offset, count);
        }

        public config.MessageToModelConfig MessageToModelConfig
        {
            get; set;
        }

        public void Close()
        {
            _client.GetStream().Close();
            _client.Close();
            _client = null;
        }
        #endregion

        public static void CloseAll()
        {
            lock (((ICollection)tcpClients).SyncRoot)
            {
                foreach (ResponseMessageSender rms in tcpClients.Values)
                {
                    rms.Close();
                }
                tcpClients.Clear();
            }
        }
    }
}
